#include "WrlIO.hpp"
#include <common.hpp>
#include "../Material.hpp"
#include <Utility/StringPrintf.hpp>
#include <Utility/FileTools.hpp>
#include <Utility/BraceFile.hpp>
#undef RGB

namespace zzz{

bool WrlIO::Load(const string &filename, TriMesh &mesh,bool load_mat)
{
  BraceFile bf;
  if (!bf.LoadFile(filename)) return false;
  mesh.Clear();
  // Actually I don't know what it should be according specification.
  // However this code can deal with some case
  // The second case is for the file generated by Peng Zhao
  BraceNode objNode=bf.GetFirstNode("Transform");
  if (!objNode.IsValid())
    objNode=bf.GetFirstNode("Group");

  BraceNode childrenNode=objNode.GetFirstNode("children");
  for (zuint g=0; g<childrenNode.NodeNumber(); g++) {
    BraceNode shapeNode=childrenNode.GetNode(g);
    mesh.groups_.push_back(new TriMesh::MeshGroup());
    TriMesh::MeshGroup *group=mesh.groups_.back();

    BraceNode appearanceNode=shapeNode.GetFirstNodeInclude("appearance");
    // material exist
    if (appearanceNode.IsValid()) {
      // create material
      Material *mat=new Material;
      string matName=GetBase(GetFilename(filename))+"_Material";
      matName<<g;
      group->matname_=matName;
      ZRM->Add(mat,matName);

      BraceNode materialNode=appearanceNode.GetFirstNodeInclude("material");
      if (materialNode.IsValid()) {
        for (zuint i=0; i<materialNode.NodeNumber(); i++) {
          istringstream iss(materialNode.GetNode(i).GetText());
          string colorname;
          iss>>colorname;
          if (colorname=="diffuseColor") {
            iss>>mat->diffuse_.RGB();
            mat->SetFlag(MAT_DIF);
          } else if (colorname=="emissiveColor") {
            iss>>mat->emission_.RGB();
            mat->SetFlag(MAT_EMI);
          } else if (colorname=="specularColor") {
            iss>>mat->specular_.RGB();
            mat->SetFlag(MAT_SPE);
          } else if (colorname=="shininess") {
            iss>>mat->shininess_;
          }
        }
      }
      BraceNode textureNode=appearanceNode.GetFirstNodeInclude("texture");
      if (load_mat && textureNode.IsValid()) {
        istringstream iss(textureNode.GetFirstNodeInclude("url").GetText());
        string storefilename;
        iss>>storefilename>>storefilename;
        if (storefilename[0]=='\"') storefilename.assign(storefilename.begin()+1,storefilename.end()-1);
        storefilename=PathFile(GetPath(filename),storefilename);
        if (GLExists()) {
          mat->ambientTex_.FileToTexture<Vector3uc>(storefilename.c_str());
          mat->SetFlag(MAT_AMBTEX);
        } else {
          mat->ambientTexName_=storefilename;
        }
      }
    }

    //position and texture coordinate
    BraceNode geometryNode=shapeNode.GetFirstNodeInclude("geometry");
    Vector3f v;
    Vector3i vi;
    string data;
    BraceNode coordPointNode=geometryNode.GetFirstNodeInclude("coord").GetFirstNode("point");
    int coordBase=mesh.pos_.size();
    coordPointNode.GetChildrenText(data);
    {
      istringstream iss(data);
      while(true) {
        iss>>v;
        if (iss.fail()) break;
        mesh.pos_.push_back(v);
      }
    }
    BraceNode coordIndexNode=geometryNode.GetFirstNode("coordIndex");
    coordIndexNode.GetChildrenText(data);
    {
      istringstream iss(data);
      while(true) {
        //support triangle only
        iss>>vi;
        if (iss.fail()) break;
        vi+=coordBase;
        group->facep_.push_back(vi);
        iss>>vi[0]; //eat -1
      }
    }
    BraceNode texPointNode=geometryNode.GetFirstNodeInclude("texCoord").GetFirstNode("point");
    if (!texPointNode.IsValid()) continue;
    int texCoordBase=mesh.tex_.size();
    texPointNode.GetChildrenText(data);
    {
      istringstream iss(data);
      v[2]=0;
      while(true) {
        iss>>v[0]>>v[1];
        if (iss.fail()) break;
        mesh.tex_.push_back(v);
      }
    }
    BraceNode texCoordIndexNode=geometryNode.GetFirstNode("texCoordIndex");
    if (!texCoordIndexNode.IsValid()) continue;
    coordIndexNode.GetChildrenText(data);
    {
      istringstream iss(data);
      while(true) {
        //support triangle only
        iss>>vi;
        if (iss.fail()) break;
        vi+=texCoordBase;
        group->facet_.push_back(vi);
        iss>>vi[0]; //eat -1
      }
    }
  }
  return true;
}

bool WrlIO::Save(const string &filename, TriMesh &mesh, bool save_mat)
{
  BraceFile bf;
  bf.AppendNode("#VRML V2.0 utf8");
  BraceNode objNode=bf.AppendNode("DEF object0 Transform",'{','}');
  BraceNode childrenNode=objNode.AppendNode("children",'[',']');
  for (zuint g=0; g<mesh.groups_.size(); g++)
  {
    BraceNode shapeNode=childrenNode.AppendNode("Shape",'{','}');
    if (save_mat && !mesh.groups_[g]->matname_.empty())
    {
      Material *mat=ZRM->Get<Material*>(mesh.groups_[g]->matname_);
      BraceNode appearanceNode=shapeNode.AppendNode("appearance Appearance", '{', '}');
      if (mat->HasFlag(MAT_DIF) || mat->HasFlag(MAT_SPE) || mat->HasFlag(MAT_EMI))
      {
        BraceNode materialNode=appearanceNode.AppendNode("material Material", '{', '}');
        if (mat->HasFlag(MAT_DIF))
          materialNode.AppendNode(string("diffuseColor ")<<mat->diffuse_.RGB());
        if (mat->HasFlag(MAT_EMI))
          materialNode.AppendNode(string("emissiveColor ")<<mat->emission_.RGB());
        if (mat->HasFlag(MAT_SPE)) {
          materialNode.AppendNode(string("specularColor ")<<mat->specular_.RGB());
          materialNode.AppendNode(string("shininess ")<<mat->shininess_);
        }
      }
      // VRML only support diffuse texture
      if (mat->HasFlag(MAT_DIFTEX)) {
        string storefilename=GetBase(filename);
        storefilename<<'_'<<mesh.groups_[g]->matname_<<"_dif.png";
        BraceNode textureNode=appearanceNode.AppendNode("texture ImageTexture", '{', '}');
        textureNode.AppendNode(StringPrintf("url \"%s\"",storefilename.c_str()));
        appearanceNode.AppendNode("textureTransform TextureTransform", '{', '}')<<"translation 0 0"<<"scale 1 1";

        storefilename=PathFile(GetPath(filename),storefilename);
        Image4uc img;
        mat->diffuseTex_.TextureToImage(img);
        ZLOG(ZDEBUG)<<"Saved texture: "<<storefilename<<" size: "<<img.Cols()<<'x'<<img.Rows()<<endl;
        img.SaveFile(storefilename.c_str());
      } else if (!mat->diffuseTexName_.empty()) {
        BraceNode textureNode=appearanceNode.AppendNode("texture ImageTexture", '{', '}');
        textureNode.AppendNode(string("url \"")+mat->diffuseTexName_+"\"");
        appearanceNode.AppendNode("textureTransform TextureTransform", '{', '}')<<"translation 0 0"<<"scale 1 1";
      }
    }

    //position and texture coordinate
    vector<Vector3f*> pos;
    vector<Vector3f*> tex;
    vector<Vector3ui> posi;
    vector<Vector3ui> texi;

    map<zuint,zuint> old_new;
    for (zuint i=0; i<mesh.groups_[g]->facep_.size(); i++) {
      Vector3ui pi;
      for (zuint j=0; j<3; j++) {
        zuint old=mesh.groups_[g]->facep_[i][j];
        map<zuint,zuint>::iterator mi=old_new.find(old);
        // new
        if (mi==old_new.end()) {
          pi[j]=pos.size();
          old_new[old]=pos.size();
          pos.push_back(&(mesh.pos_[old]));
        } else
          pi[j]=mi->second;
      }
      posi.push_back(pi);
    }
    old_new.clear();
    for (zuint i=0; i<mesh.groups_[g]->facet_.size(); i++) {
      Vector3ui ti;
      for (zuint j=0; j<3; j++) {
        zuint old=mesh.groups_[g]->facet_[i][j];
        map<zuint,zuint>::iterator mi=old_new.find(old);
        // new
        if (mi==old_new.end()) {
          ti[j]=tex.size();
          old_new[old]=tex.size();
          tex.push_back(&(mesh.tex_[old]));
        } else
          ti[j]=mi->second;
      }
      texi.push_back(ti);
    }

    BraceNode geometryNode=shapeNode.AppendNode("geometry IndexedFaceSet", '{', '}');
    BraceNode coordPointNode=geometryNode.AppendNode("coord Coordinate", '{', '}').AppendNode("point", '[', ']');
    for (zuint i=0; i<pos.size(); i++) {
      string line;
      line<<*(pos[i]);
      coordPointNode.AppendNode(line.c_str());
    }
    BraceNode texPoint=geometryNode.AppendNode("texCoord TextureCoordinate",'{','}').AppendNode("point",'[',']');
    for (zuint i=0; i<tex.size(); i++) {
      string line;
      line<<tex[i]->at(0)<<' '<<tex[i]->at(1);
      texPoint.AppendNode(line.c_str());
    }
    BraceNode coordIndexNode=geometryNode.AppendNode("coordIndex",'[',']');
    for (zuint i=0; i<posi.size(); i++) {
      string line;
      line<<posi[i]<<" -1";
      coordIndexNode.AppendNode(line.c_str());
    }
    BraceNode texCoordIndexNode=geometryNode.AppendNode("texCoordIndex",'[',']');
    for (zuint i=0; i<texi.size(); i++) {
      string line;
      line<<texi[i]<<" -1";
      texCoordIndexNode.AppendNode(line.c_str());
    }
  }
  return bf.SaveFile(filename);

}
//bool WrlIO::Load(const string &filename, SimpleMesh &mesh);
//bool WrlIO::Save(const string &filename, SimpleMesh &mesh);


}

